/**
 * Validation module
 *
 * JQUERY versions 1.7+
 * 
 * Green CMS - Content Management System and Framework Powerfull by Cakephp
 * Copyright 2012, GREEN GLOBAL CO., LTD (toancauxanh.vn)
 * 
 * jQuery(tm) Copyright 2011, John Resig http://jquery.org/license
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @author        Tien Van Vo <vantienvnn@gmail.com>
 * @link          
 * @package       Green.Theme
 * @since         Green v 1.0
 * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
 */
Green.Validator = Green.Validator || {
    expando : "tvpanel-validator",
    css:{
        failed: "validation-failed",
        passed : "validation-passed",
        wait:"validation-wait",
        mask:"validation-mask",
        notice:"validation-notice"
    },
    methods:{},
    allows:{
        notempty : true,
        equaltofield : true
    },
    behaviors:{},
    pattern:{
        hostname : "(?:[a-z0-9][-a-z0-9]*\\.)*(?:[a-z0-9][-a-z0-9]{0,62})\\.(?:(?:[a-z]{2}\\.)?[a-z]{2,4}|museum|travel)",
        IPv4:"(?:(?:25[0-5]|2[0-4][0-9]|(?:(?:1[0-9])?|[1-9]?)[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|(?:(?:1[0-9])?|[1-9]?)[0-9])",
        IPv6:"((([0-9A-Fa-f]{1,4}:){7}(([0-9A-Fa-f]{1,4})|:))|(([0-9A-Fa-f]{1,4}:){6}(:|((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})|(:[0-9A-Fa-f]{1,4})))|(([0-9A-Fa-f]{1,4}:){5}((:((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){4}(:[0-9A-Fa-f]{1,4}){0,1}((:((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){3}(:[0-9A-Fa-f]{1,4}){0,2}((:((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}((:((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(([0-9A-Fa-f]{1,4}:)(:[0-9A-Fa-f]{1,4}){0,4}((:((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(:(:[0-9A-Fa-f]{1,4}){0,5}((:((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})?)|((:[0-9A-Fa-f]{1,4}){1,2})))|(((25[0-5]|2[0-4]\\d|[01]?\\d{1,2})(\\.(25[0-5]|2[0-4]\\d|[01]?\\d{1,2})){3})))(%.+)?"
    }
};
(function ($) {
    var $this = Green.Validator;
    $.extend($this, {
        /**
         * Add new validate method
         * 
         * @param methods all validate functions to add 
         * 
         * return void
         */
        add:function(methods){
            $.extend($this.methods,methods);
        },
        /**
         * Allow method for empty value check
         * 
         * @param methods maps method allowed
         * 
         * return void
         */
        allow:function(methods){
            $.extend($this.allows,methods);
        },
        /**
         * Message format
         * 
         * @param message
         * @param args the format arguments
         * 
         * return string message
         * @see Green.format
         */
        format:function(message,args){
            if(message===true){
                return "";
            }
            if(args === undefined){
                return message;
            }
            if (!$.isArray(args)) {
                args = $.makeArray(arguments);
                args.shift();
            }
            return Green.format(message, args);
        },
        /**
         * RegExp check
         * 
         * @param value the value to check
         * @param pattern the RegExp pattern
         * @param modifiers the RegExp modifiers
         * 
         * return boolean
         */
        regex:function(value,pattern,modifiers){
            var regex=new RegExp(pattern,modifiers);
            if(regex.test(value)){
                return true;
            }
            return false;
        },
        /**
         * attach a behavior
         * 
         * @param behavior
         * @param methods
         * 
         * return void
         */
        attach:function(behavior,methods){
            if(!$this.behaviors[behavior]){
                $this.behaviors[behavior] = {};
            }
            $.extend($this.behaviors[behavior],methods);
        },
        /**
         * Attempts to pass unhandled Validation locales to a class starting with classPrefix
         * and ending with Validation.
         * 
         * @param jQuery $element the jQuery Dom Element
         * @param behavior
         * @param method
         * @param params
         * 
         * @return mixed boolean true on validate, string message on invalidate
         * @see this.attach
         */
        trigger:function(behavior,method,$element,params){
            if($this.behaviors[behavior] && $.isFunction($this.behaviors[behavior][method])){
                return $this.behaviors[behavior][method].apply($element,params||[]);
            }
            throw "An error when try pass to method "+ method+" of "+ behavior +" behavior";
            return false;
        },
        /**
         * Get or set rule
         * 
         * @param jQuery $element the jQuery Dom Element
         * @param object adds rule options to add
         * @param mixed removes, array keys to remove or boolean true for remove all key
         * 
         * return current rules of Dom Element
         */
        rules:function($element,adds,removes){
            var data = $element.data($this.expando) || {};
            if(adds){
                for(var key in adds){
                    if($.isArray(adds[key])){
                        adds[key] = {
                            params:adds[key]
                        };
                    }else{
                        if($.type(adds[key])!="object"){
                            adds[key] = {
                                message:adds[key]
                            };
                        }
                    }
                    data[key] = adds[key];
                }
            }
            if(removes){
                if(removes==true){
                    data = {};
                }else{
                    $.each($.makeArray(removes),function(i,v){
                        delete data[v];
                    });
                }
            }
            if(adds || removes){
                $element.data($this.expando,data);
            }
            return $.extend({},data);
        },
        /** 
         * Load data to validate
         * options onLoading:function(){} & onComplete:function(transport){} also bind this content
         *
         **/
        load:function($element,params,options){
            var expando = "load" + params.type + "-" +$this.expando;
            var data = $element.data(expando);
            if(data && $.inArray(params.value, data) != -1){
                return true;
            }
            options = $.extend({
                type:"GET",
                data : {},
                beforeSend:function(){},
                success:function(){},
                error:function(){}
            },options || {});
            $.extend(options.data, params);
            $.ajax($.extend({},options,{
                beforeSend:function(xhr, sets){
                    $this.notice($element).addClass($this.css.wait);
                    options.beforeSend.call($element,xhr, sets);
                },
                success:function(result, status, xhr){
                    $this.notice($element).removeClass($this.css.wait);
                    if(result=="ok"){
                        $this.hide($element);
                        $element.data(expando,$.merge($element.data(expando) || [], [params.value]));
                    }else{
                        $this.show($element, $this.format(options.message || Green.t("[<b>{0}</b>] is already in use. Please choose another name."),params.value));
                    }
                    options.success.call($element,result, status, xhr);
                },
                error:function(xhr, status, thrown){
                    $this.show($element, Green.t("Could not connect to the server."));
                    options.error.call($element,xhr, status, thrown);
                }
            }));
            return Green.t("Please wait while connecting to the server ...");
        },
        /**
         * Check value is empty
         * 
         * @param value the value to check
         * 
         * return boolean
         */
        isEmpty:function(value){
            return (value==null || value==undefined || value.length==0);
        },
        /**
         * Check jQuery Dom Element is validate with rule configed
         * 
         * @param $element the jQuery Dom Element
         * 
         * return boolean
         */
        validate:function($element){
            var $form = $element.closest("form"),rules = {};
            var getInputs = function(type){
                var result = [],name = $element.attr('name');
                if($element.is(":"+type) && name){
                    rules = $this.rules($("input[name='"+name.replace(/\[\]$/, "")+"']"));
                    $form.find("input[type="+type+"][name='"+name+"']:checked").each(function(){
                        result.push($(this).val());
                    });
                }
                return result.length ? result : null;
            };
            
            if(!$element.is(":hidden")){
                $this.removeMask($element);
                var message=true,
                check = (getInputs("radio") || getInputs("checkbox") || $element.val()),
                notEmpty = !$this.isEmpty(check);
                
                if($.isEmptyObject(rules)){
                    rules = $this.rules($element); 
                }
                
                $.each(rules, function(key,rule){
                    if($this.allows[key] || notEmpty){
                        if(rule.online){
                            message = $this.load($element,{
                                value:check,
                                type:"validator",
                                key:key
                            },rule);
                        }else{
                            if(!$this.methods[key]){
                                key = "custom";
                            }
                            if($this.methods[key]){
                                if(rule.message!=undefined && !rule.message){
                                    rule.message = true;
                                }
                                message = $this.methods[key].apply($element,$.merge([check, rule.message] , rule.params || []));
                            }
                        }
                        if(message!==true){
                            return false;
                        }
                    }
                });
                
                if(message!==true){
                    $this.show($element,message);
                    return false;
                }
                $this.hide($element);
            }
            return true;
        },
        /**
         * Show error message
         * 
         * @param $element the jQuery Dom Element
         * 
         * return $notice the notice element
         */
        show:function($element,message){
            var $notice = $this.notice($element).html(message);
            if(message){
                if($notice.is(":hidden")){
                    $notice.show();
                }
            }else{
                $notice.hide();
            }
            $element.removeClass($this.css.passed).addClass($this.css.failed);
            return $notice;
        },
        /**
         * Hide error message
         * 
         * @param $element the jQuery Dom Element
         * 
         * return $notice the notice element
         */
        hide:function($element){
            var $notice = $this.notice($element).hide();
            $element.removeClass($this.css.failed).addClass($this.css.passed);
            return $notice;
        },
        notice:function($element){
            var $parent = $element.closest(".input");
            var $notice= $parent.length?$parent.find("."+$this.css.notice).last() : $element.next("."+$this.css.notice);
            if(!$notice.length){
                $notice = $("<div class=\""+$this.css.notice+"\" style=\"display:none\" />");
                if($parent.length){
                    $parent.append($notice);
                }else{
                    $element.after($notice);
                }
            }
            return $notice;
        },
        /* Make mask text for input */
        addMask:function($element,title){
            var value = $element.val();
            if(!value || value==title){
                $element.val(title).addClass($this.css.mask);
                var handler = function() {
                    $this.removeMask($element);
                };
                $element.unbind("focus",handler).bind("focus",handler);
            }
        },
        removeMask:function($element){
            if($element.hasClass($this.css.mask)){
                $element.val("").removeClass($this.css.mask);
            }
        },
        methods:{
            /**
             * Apply all methods
             * 
             * In function of methods when called has bind the jQuery Dom Element
             * 
             * @param mixed value the string or array value
             * @param object options the rule options
             * @param jQuery the jQuery Dom element
             * 
             * @return mixed boolean true on validate, string message on invalidate
             **/
            notempty:function(value,message){
                return !$this.isEmpty(value) || $this.format(message || Green.t("This is a required information."));
            },
            blank:function(value,message) {
                return (value == "" || /^\s*$/.test(value)) || $this.format(message || Green.t("This information is left blank."));
            },
            alphanumeric:function(value,message){
                return !/\W/.test(value) || $this.format(message || Green.t("This information must only contain letters and numbers."));
            },
            range:function(value,message,min,max){
                return ($.isNumeric(value) && value > min && value < max) || $this.format(message || Green.t("Please enter a number in range {0} and {1}."), min, max);
            },
            multiple:function(value,message,min,max,allow){
                if(!$.isArray(value)){
                    value=[value];
                }
                var vaild = true;
                if(allow){
                    var arr =  $.makeArray(allow);
                    $.each(value,function(i,v){
                        if($.inArray(v,arr) == -1){
                            vaild = false;
                            return false;
                        }
                    });
                }
                return (vaild && (!max || value.length<=max) && (!min || value.length>=min)) || $this.format(message || Green.t("Please select from {0} to {1} of the above options."),min||0,max||"x");
            },
            between:function(value,message,min,max){
                return (value.length >= min && value.length <= max) || $this.format(message || Green.t("This information must be between {0} and {1} characters long."),min,max);
            },
            comparison:function(value,message,operator,value2){
                var result = $.type(operator) == "string" && $.isNumeric(value);
                if(result){
                    value = parseFloat(value);
                    value2 = parseFloat(value2);
                    switch (operator.toLowerCase()) {
                        case "is greater":
                        case ">":
                            result= (value > value2);
                            break;
                        case "is less":
                        case "<":
                            result= (value < value2);
                            break;
                        case "greater or equal":
                        case ">=":
                            result= (value >= value2);
                            break;
                        case "less or equal":
                        case "<=":
                            result= (value <= value2);
                            break;
                        case "equal to":
                        case "==":
                            result= (value == value2);
                            break;
                        case "not equal":
                        case "!=":
                            result= (value != value2);
                            break;
                        default:
                            result= false;
                    }
                }
                return result || $this.format(message || Green.t("Please enter a number {0} {1}."),operator,value2);
            },
            money:function(value,message,position){
                var pattern = /^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/;
                if(position=="right"){
                    pattern = /^\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)\$?$/;
                }
                return $this.regex(value,pattern) || $this.format(message || Green.t("Please enter a valid $ amount. For example {0} or {1}."),(position!="right"?"$":"")+"100,530,200.02"+(position=="right"?"$":""),"100,530,200.02");
            },
            uuid:function(value,message){
                return $this.regex(value,/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i) || $this.format(message || Green.t("Please enter a valid universally unique identifier."));
            },
            phone:function(value,message,pattern,country,example){
                if(!pattern){
                    switch(country || "us"){
                        case "us":{
                            pattern=/^(?:\+?1)?[-. ]?\(?[2-9][0-8][0-9]\)?[-. ]?[2-9][0-9]{2}[-. ]?[0-9]{4}$/;
                        }
                    }
                }
                if(!pattern){
                    return $this.trigger(country,"phone",this,[value,message]);
                }
                return $this.regex(value,pattern) || $this.format(message || Green.t("Please enter a valid phone number.{0}"),example || "");
            },
            postal:function(value,message,pattern,country,example){
                if(!pattern){
                    switch(country || "us"){
                        case "us":{
                            pattern=/^(?:\+?1)?[-. ]?\(?[2-9][0-8][0-9]\)?[-. ]?[2-9][0-9]{2}[-. ]?[0-9]{4}$/;
                        }
                    }
                }
                if(!pattern){
                    return $this.trigger(country,"postal",this,[value,message]);
                }
                return $this.regex(value,pattern) || $this.format(message || Green.t("Please enter a valid postal number.{0}"),example || "");
            },
            datetime:function(value,message,format,pattern){
                var result = false;
                value =  value.split(" ");
                if(value.length>1){
                    var time = value.pop();
                    result = ($this.methods.date.call(this,value.join(" "),true,format,pattern)===true && $this.methods.time.call(this,time,true)===true);
                }
                return result || $this.format(message || Green.t("Please enter a valid date time."));
            },
            date:function(value,message,format,pattern){
                if(!pattern){
                    var patterns={
                        dmy: /^(?:(?:31(\/|-|\.| )(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.| )(?:0?[1,3-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.| )0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.| )(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$/i,
                        mdy: /^(?:(?:(?:0?[13578]|1[02])(\/|-|\.| )31)\1|(?:(?:0?[13-9]|1[0-2])(\/|-|\.| )(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:0?2(\/|-|\.| )29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.| )(?:0?[1-9]|1\d|2[0-8])\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$/i,
                        ymd: /^(?:(?:(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))(\/|-|\.| )(?:0?2\1(?:29)))|(?:(?:(?:1[6-9]|[2-9]\d)?\d{2})(\/|-|\.| )(?:(?:(?:0?[13578]|1[02])\2(?:31))|(?:(?:0?[1,3-9]|1[0-2])\2(29|30))|(?:(?:0?[1-9])|(?:1[0-2]))\2(?:0?[1-9]|1\d|2[0-8]))))$/i,
                        dMy: /^((31(?!\ (Feb(ruary)?|Apr(il)?|June?|(Sep(?=\b|t)t?|Nov)(ember)?)))|((30|29)(?!\ Feb(ruary)?))|(29(?=\ Feb(ruary)?\ (((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00)))))|(0?[1-9])|1\d|2[0-8])\ (Jan(uary)?|Feb(ruary)?|Ma(r(ch)?|y)|Apr(il)?|Ju((ly?)|(ne?))|Aug(ust)?|Oct(ober)?|(Sep(?=\b|t)t?|Nov|Dec)(ember)?)\ ((1[6-9]|[2-9]\d)\d{2})$/i,
                        Mdy : /^(?:(((Jan(uary)?|Ma(r(ch)?|y)|Jul(y)?|Aug(ust)?|Oct(ober)?|Dec(ember)?)\ 31)|((Jan(uary)?|Ma(r(ch)?|y)|Apr(il)?|Ju((ly?)|(ne?))|Aug(ust)?|Oct(ober)?|(Sept|Nov|Dec)(ember)?)\ (0?[1-9]|([12]\d)|30))|(Feb(ruary)?\ (0?[1-9]|1\d|2[0-8]|(29(?=,?\ ((1[6-9]|[2-9]\d)(0[48]|[2468][048]|[13579][26])|((16|[2468][048]|[3579][26])00)))))))\,?\ ((1[6-9]|[2-9]\d)\d{2}))$/i,
                        My : /^(Jan(uary)?|Feb(ruary)?|Ma(r(ch)?|y)|Apr(il)?|Ju((ly?)|(ne?))|Aug(ust)?|Oct(ober)?|(Sep(?=\b|t)t?|Nov|Dec)(ember)?)[ /]((1[6-9]|[2-9]\d)\d{2})$/i,
                        my : /^(((0[123456789]|10|11|12)([- /.])(([1][9][0-9][0-9])|([2][0-9][0-9][0-9]))))$/i
                    };
                    if(!format){
                        format = "ymd";
                    }
                    pattern=patterns[format];
                }
                return $this.regex(value,pattern) || $this.format(message || Green.t("Please use this date format: {0}."),format);
            },
            time:function(value,message){
                return $this.regex(value,/^((0?[1-9]|1[012])(:[0-5]\d){0,2}([AP]M|[ap]m))$|^([01]\d|2[0-3])(:[0-5]\d){0,2}$/i) || $this.format(message || Green.t("Enter a valid time. For example 11:12am or 11:12"));
            },
            decimal:function(value,message,places,pattern){
                if(!pattern){
                    places = parseInt(places);
                    if(places > 1){
                        pattern = "^[-+]?[0-9]*\\.{1}[0-9]{"+places+"}$";
                    }else{
                        pattern="^[-+]?[0-9]*\\.{1}[0-9]+(?:[eE][-+]?[0-9]+)?$";
                    }
                }
                return $this.regex(value,pattern) || $this.format(message || Green.t("Enter a valid decimal number. For example 2.{0}"),Green.repeat("0",places || 1));
            },
            email:function(value,message,pattern){
                if(!pattern){
                    pattern = new RegExp("^[a-z0-9!#$%&'*+\\/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+\\/=?^_`{|}~-]+)*@" + $this.pattern.hostname + "$","i");
                }
                return $this.regex(value,pattern) || $this.format(message || Green.t("Please enter a valid email address."));
            },
            equaltofield:function(value,message,fieldId){
                return (value==$("#"+fieldId).val()) || $this.format(message || Green.t("Your confirmation does not match your first entered"));
            },
            equalto:function(value,message,value2){
                return (value==value2) || $this.format(message || Green.t("This value must be the string {0}"),value2);
            },
            extension:function(value,message,exts){
                if($.isArray(value)){
                    value = value.shift();
                }
                exts = exts ? $.map($.makeArray(exts),function(v){
                    return v.toLowerCase();
                }) : ["gif", "jpeg", "png", "jpg"];
                value = (value.match(/\.[0-9a-z]*$/i)||[""])[0].replace(".","").toLowerCase();
                return (value && $.inArray(value,exts) != -1) || $this.format(message || Green.t("Please supply a valid extension in these {0}"),exts.join(", "));
            },
            minlength:function(value,message,length){
                return (value.length >= length) || $this.format(message || Green.t("Please enter at least {0} characters"),length);
            },
            maxlength:function(value,message,length){
                return (value.length <= length) || $this.format(message || Green.t("Please enter must be no larger than {0} characters long."),length);
            },
            numeric:function(value,message){
                return $.isNumeric(value) || $this.format(message || Green.t("Please enter a valid number."));
            },
            url:function(value,message,strict){
                var validChars="([\\!\"\\$&'\\(\\)\\*\\+,\\-\\.@_\\:;\\=~\\/0-9a-z]|(%[0-9a-f]{2}))";
                return $this.regex(value,"^(?:(?:https?|ftps?|file|news|gopher):\\/\\/)" + (strict ? "" : "?") + "(?:" + $this.pattern.IPv4 + "|\\[" + $this.pattern.IPv6 + "\\]|" + $this.pattern.hostname + ")" + "(?::[1-9][0-9]{0,4})?" + "(?:\\/?|\\/" + validChars + "*)?" + "(?:\\?" + validChars + "*)?"+"(?:#" + validChars + "*)?$","i") 
                || $this.format(message || Green.t("Please enter a valid URL."));
            },
            ip:function(value,message,type){
                type = (type || "both").toLowerCase();
                var result=false;
                if (type == "ipv4" || type == "both") {
                    result = $this.regex(value,"^"+$this.pattern.IPv4+"$");
                }
                if (type == "ipv6" || (type == "both" && !result)) {
                    result = $this.regex(value,"^"+$this.pattern.IPv6+"$");
                }
                return result || $this.format(message || Green.t("Please enter a valid ip address."));
            },
            custom:function(value,message,pattern) {
                if(!pattern || typeof(pattern)!="object") {
                    pattern =  pattern.toString();
                }
                return $this.regex(value,pattern) || $this.format(message || Green.t("Validation failed."));
            }
        }
    });
    /**
         * Add new validate method
         * 
         * @param form all validate functions to add 
         * @param options
         * 
         * return validate instance
         */
    
    Green.Validate = Green.Validate ||  function(form,maps,options){
        this.form = form;
        this.options = $.extend({
            submit:true,
            focus:true,
            onValidate:function(result){}
        },options||{});
        this.init();
        this.add(maps);
    };
    $.extend(Green.Validate,{
        prototype : {
            init:function(){
                var $_this = this;
                this.form = $(this.form);
                
                if(this.options.submit){
                    this.form.submit(function(){
                        return $_this.check();
                    });
                }
                this.form.find(":input").each(function(i,input){
                    var $element = $(input),title = $element.attr("title");
                    if(title){
                        $this.addMask($element,title);
                    }
                    var handler = function() {
                        $this.validate($element);
                    };
                    $element.unbind("blur",handler).bind("blur",handler);
                });
            },
            add:function(maps){
                $.each(maps || {},function(id,rules){
                    $this.rules($("#"+id),rules);
                });
            },
            remove:function(maps){
                $.each(maps,function(id,rules){
                    $this.rules($("#"+id),null,rules);
                });
            },
            check:function(){
                var result=true;
                this.form.find(":input").each(function(i,input){
                    result = $this.validate($(input));
                    return result;
                });
                if(!result && this.options.focus){
                    this.form.find(":input").each(function(i,input){
                        var $element = $(input);
                        return (!$element.hasClass($this.css.failed) || !$element.focus());
                    });
                }
                this.options.onValidate.call(this.form,result);
                return result;
            },
            reset:function(){
                this.form.find(":input").each(function(i,input){
                    $this.hide($(input).removeClass($this.css.passed));
                });
                this.form[0].reset();
            }
        }
    });
    $.extend($.fn, {
        validate:function(maps,options){
            if(!this.length){
                return;
            }
            this.attr("novalidate", "novalidate");
            var instance = $.data(this[0],$this.expando);
            if(!instance){
                instance = new Green.Validate(this[0],maps,options);
                $.data(this[0],$this.expando,instance);
            }
            return instance;
        }
    });
    
/** 
     * Example for attach a behavior
     *
     *{{{
     *   $this.attach("vi",{
     *      phone:function(value,message){
     *          return $this.regex(value,/^0[2-8][0-9]{0,2}(?:[-. ]{0,1})[0-9]{6,7}$/) || $this.format(message || Green.t("Please enter a valid phone number, for example: 0511 3888888 or 05113888888."));
     *      }
     *   });
     *   $this.attach("en",{
     *      ...
     *   });
     *}}}
    **/
})(jQuery);